The Scheme Debugger: Debug

A breakpoint will allow you only to examine local variables and to evaluate expressions at the breakpoint. Often, however, tracking down a bug will require a more extensive investigation of the circumstances surrounding the error. The Scheme debugger system provides additional capabilities to aid in debugging.

The history examiner, entered using the debug command, enables you to examine the history of execution of an expression. The environment of any procedure application may then be examined and the interrupted process may be continued from any of the displayed nodes of evaluation.

Calling the debug procedure from within a Scheme breakpoint enters a read-execute loop, which accepts single-character commands which enable you to move backwards and forwards in the history of the expressions which have been evaluated. You can also examine procedures and variables at the different evaluation levels. The prompt is Debug. Just as with where, Scheme prompts for the commands in the mode line at the bottom of the screen.

In order to make good use of the debugger, you must understand the concept of a ndexfile(index-entry "reduction" "rm" main )reduction and a ndexfile(index-entry "subproblem" "rm" main )subproblem. The interpreter evaluates an expression by reducing it to a simpler expression. In general, Scheme's evaluation rules designate that evaluation proceeds from one expression to the next by either starting to work on a subexpression of the given expression, or by reducing the entire expression to a new (simpler, or reduced) form. Thus, a history of the successive forms processed during the evaluation of an expression will show a sequence of subproblems, where each subproblem may involve a sequence of reductions. The best way to get a feeling for how reductions and subproblems work is to write some test expressions and play with them using the debug procedure.

Here is a summary of the debug commands.

?
Prints a summary of the debug commands.

B
(for back) Moves back to the ``previous expression,'' that was being processed. What this means is as follows: One goes backwards along the sequence of reductions that produced the current expression until reaching the subproblem that led to this chain of reductions. Then one moves ``upward'' in the tree of subproblems, until reaching the expression that was typed at the REP loop which initiated the whole process.

C
(for current) Shows all the variable bindings in the environment in which the current subproblem was being evaluated.

D
(for down) Moves down to a lower subproblem level. This is the subproblem which was evaluated after (in time) the current one.

E
(for eval) Enters a REP loop in the environment in which the current subproblem was being evaluated.

F
(for forward) Moves to the ``next'' expression after the current one. The order is (the opposite of) the one described for the B command.

G
(for go) Goes to a particular subproblem and reduction level. Debug asks you for the numbers. Each expression has two numbers to indicate a subproblem and a reduction of that subproblem. Simplified (more reduced) expressions have lower reduction numbers. Lower level (smaller subproblem) expressions have lower subproblem numbers. Thus the current error expression is numbered (0, 0).

H
(for history) Prints a summary of the entire history. This is like typing B repeatedly.

I
(for information) Redisplays the error message.

L
(for list current expression) Pretty-prints the current expression.

P
(for procedure) Pretty-prints current procedure.

Q
(for quit) Exits debug returning to the environment in which the error occurred, and at the expression which caused the error.

R
(for reductions) Prints all the reductions of the current subproblem level.

S
(for subproblem) Prints the current subproblem and reduction showing where in the evaluation history the user is.

U
(for up) Moves up to the previous subproblem level. This is a subproblem which includes as one of its parts the current subproblem. It has a higher subproblem number.

V
(for value) Asks for an expression and evaluates it in the environment in which the current subproblem was being evaluated.

W
(for where) Enters where on the current environment

Z
(for zap) Asks for an expression and evaluates it in the current environment. The value of the expression is used in place of the value the current subproblem would have returned, and execution is resumed from this point in the original evaluation.

For a first look at the debugger we will use several of the common commands just to see how they work. Here is a demonstration. $\Longrightarrow$
$\Longrightarrow$ unspecified error `=̀13`


          [1 REP] (define (fact n)           (if (zero? n)               l               (* n (fact (-1+ n))))) FACT 

[1 REP] (fact 3) Unbound Variable L Error!

[2 Error] (debug) Subproblem Level: 0 Reduction Number: 0 Expression: L within the procedure FACT. applied to (0)

[3 Debug]

The application of the procedure fact caused an error. We then called the debugger, which printed the expression whose evaluation caused the error to occur. The ``Subproblem Level'' and the ``Reduction Number'' are ways of numbering expressions in the history. The procedure whose body is currently being evaluated, as well as the arguments passed to it are displayed. The debugger accepts one letter commands which can be listed by typing ?.

The factorial problem above has a simple typo; the letter L is used instead of the number 1 in the if form, resulting in an unbound variable error. The debugger indicates that the error occurred when fact was called with 0 as its argument.

Although the bug in this case is easy to spot, we can look through the evaluation history, to see how the evaluation proceeds. The command H lists a summary of the history.

$\Longrightarrow$
$\Longrightarrow$ unspecified error `=̀13`


          

[3 Debug] h Sub Prb. Procedure Name Expression

0 FACT L 0 FACT (IF (ZERO? N) L (* N (FACT (-1+ N)))) 0 FACT (FACT (-1+ N)) 1 FACT (* N (FACT (-1+ N))) 2 FACT (* N (FACT (-1+ N))) 3 FACT (* N (FACT (-1+ N))) 4 (PRINT (SCODE-EVAL SCODE USER-CURRENT-ENVI ... 5 (SEQUENCE (SETUP-HISTORY! MAX-SUBPROBLEMS ... 6 LOOP (LOOP (CATCH (LAMBDA (AGAIN) (FLUID-LET (( ... 7 DRIVER-LOOP (DRIVER-LOOP (CATCH (LAMBDA (QUIT) (SET! T ... 8 STARTUP-TOP-LEVEL-DR... (SEQUENCE (CATCH (LAMBDA (CONT) (SET! TOP- ...

[3 Debug]Quit! Level: 1

Lines 4 through 8 printed by the examiner are pieces of the Scheme REP loop implementation itself, and should be ignored.

The most common error that can be corrected easily from the debugger is an unbound variable error. In this case, there are two possibilities. If you forgot to define the variable you can use the B command to search back through the history until you find the place where the variable should have been defined. Then, use the E command to enter a REP loop, and define the variable.